home *** CD-ROM | disk | FTP | other *** search
/ PC Pro 2005 June (DVD) / DPPRO0605DVD.iso / Install / program files / Borland / BDS / 3.0 / Demos / Delphi.Net / CLR / Chat / SocketManager.pas < prev   
Encoding:
Pascal/Delphi Source File  |  2004-10-22  |  12.9 KB  |  424 lines

  1. unit SocketManager;
  2. {*******************************************************************************
  3.   TCP/IP Chat Demo
  4.   Written by David Clegg, davidclegg@optusnet.com.au.
  5. *******************************************************************************}
  6.  
  7. interface
  8.  
  9. uses
  10.   System.Net.Sockets, System.Threading, System.Collections;
  11.  
  12. type
  13.   TByteArray = array[0..255] of Byte;
  14.  
  15.   TDataReceivedEvent = procedure(Sender: TObject; const pData: string) of object;
  16.   TErrorEvent = procedure(Sender: TObject; E: Exception) of object;
  17.   TSocketEvent = procedure(Sender: TObject; pSocket: Socket);
  18.  
  19.   TStateObject = class
  20.   strict private
  21.     FWorkSocket: Socket;
  22.     FBufferSize: integer;
  23.     //FBuffer: TByteArray;
  24.   public
  25.     //The buffer must be a public field otherwise it will not be populated when
  26.     //being passed to methods such as Socket.BeginReceive
  27.     Buffer: TByteArray;
  28.     //property Buffer: TByteArray read FBuffer write FBuffer;
  29.     property WorkSocket: Socket read FWorkSocket write FWorkSocket;
  30.     property BufferSize: Integer read FBufferSize write FBufferSize;
  31.     constructor Create(pWorkSocket: Socket);
  32.   end;
  33.  
  34.   TSocketManager = class
  35.   private
  36.     FClientSocket: Socket;
  37.     FServerSocket: Socket;
  38.     FOnClientConnected: TSocketEvent;
  39.     FOnClientDisconnected: TSocketEvent;
  40.     FStopEvent: ManualResetEvent;
  41.     FConnectEvent: ManualResetEvent;
  42.     FTransmitLock: ReaderWriterLock;
  43.     FReceiveLock: ReaderWriterLock;
  44.     FTransmitQueue: Queue;
  45.     FOnDataReceived: TDataReceivedEvent;
  46.     FOnSendError: TErrorEvent;
  47.     FOnReceiveError: TErrorEvent;
  48.     FOnListenError: TErrorEvent;
  49.     FOnConnectError: TErrorEvent;
  50.     FListening: boolean;
  51.     FConnected: boolean;
  52.     procedure SendThreadEntryPoint(pState: TObject);
  53.     procedure ReceiveThreadEntryPoint(pState: TObject);
  54.     procedure ListenThreadEntryPoint(pState: TObject);
  55.     procedure SendCallback(pAsyncResult: IAsyncResult);
  56.     procedure ReceiveCallback(pAsyncResult: IAsyncResult);
  57.     procedure AcceptCallback(pAsyncResult: IAsyncResult);
  58.     procedure InitResetEvents;
  59.   public
  60.     property OnDataReceived: TDataReceivedEvent read FOnDataReceived write FOnDataReceived;
  61.     property OnSendError: TErrorEvent read FOnSendError write FOnSendError;
  62.     property OnReceiveError: TErrorEvent read FOnReceiveError write FOnReceiveError;
  63.     property OnListenError: TErrorEvent read FOnListenError write FOnListenError;
  64.     property OnConnectError: TErrorEvent read FOnConnectError write FOnConnectError;
  65.     property OnClientConnected: TSocketEvent read FOnClientConnected write FOnClientConnected;
  66.     property OnClientDisconnected: TSocketEvent read FOnClientDisconnected write FOnClientDisconnected;
  67.     property ClientSocket: Socket read FClientSocket;
  68.     property ServerSocket: Socket read FServerSocket;
  69.     property Connected: boolean read FConnected write FConnected;
  70.     property Listening: boolean read FListening write FListening;
  71.     procedure Listen;
  72.     procedure StopListening;
  73.     procedure Connect(const pAddress: string; const pPort: integer);
  74.     procedure Disconnect;
  75.     procedure SendText(const pText: string);
  76.     constructor Create;
  77.   end;
  78.  
  79. implementation
  80.  
  81. uses
  82.   System.Net, System.Text, SysUtils, System.Windows.Forms;
  83.  
  84. { TStateObject }
  85. constructor TStateObject.Create(pWorkSocket: Socket);
  86. begin
  87.   inherited Create;
  88.   FBufferSize := 256;
  89.   FWorkSocket := pWorkSocket;
  90. end;
  91.  
  92. { TSocketManager }
  93. constructor TSocketManager.Create;
  94. begin
  95.   inherited Create;
  96.   InitResetEvents;
  97. end;
  98.  
  99. /// <summary>
  100. /// Create the objects required to control the threads.
  101. /// </summary>
  102. procedure TSocketManager.InitResetEvents;
  103. begin
  104.   //Create Reset Event instances
  105.   FStopEvent := ManualResetEvent.Create(False);
  106.   FConnectEvent := ManualResetEvent.Create(False);
  107.  
  108.   //Create ReaderWriterLock instances
  109.   FTransmitLock := ReaderWriterLock.Create;
  110.   FReceiveLock := ReaderWriterLock.Create;
  111.  
  112.   //Create Transmit and Receive Queue instances
  113.   FTransmitQueue := Queue.Create;
  114. end;
  115.  
  116. /// <summary>
  117. /// Listen for client connections on the server socket.
  118. /// </summary>
  119. procedure TSocketManager.Listen;
  120. var
  121.   lEndPoint: IPEndPoint;
  122.   lHostEntry: IPHostEntry;
  123. begin
  124.   try
  125.     FStopEvent.Reset;
  126.     lHostEntry := Dns.Resolve(Dns.GetHostName);
  127.     if Length(lHostEntry.AddressList) <> 0 then
  128.     begin
  129.       lEndPoint := IPEndPoint.Create(lHostEntry.AddressList[0], 1024);
  130.       FServerSocket := Socket.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.TCP);
  131.       FServerSocket.Bind(lEndPoint);
  132.  
  133.       //Pool the Listen thread entry point
  134.       ThreadPool.QueueUserWorkItem(ListenThreadEntryPoint);
  135.       FListening := True;
  136.     end;
  137.   except
  138.     on E: Exception do
  139.       if Assigned(FOnListenError) then
  140.         FOnListenError(Self, E);
  141.   end;
  142. end;
  143.  
  144. /// <summary>
  145. /// Signal to the server worker threads that they should stop.
  146. /// </summary>
  147. procedure TSocketManager.StopListening;
  148. begin
  149.   FStopEvent.&Set;
  150.   FListening := False;
  151. end;
  152.  
  153. /// <summary>
  154. /// Connect to the TCP/IP server.
  155. /// </summary>
  156. /// <param name="pAddress">Address of TCP/IP Server to connect to.</param>
  157. /// <param name="pPort">Port of TCP/IP Server to connect to.</param>
  158. procedure TSocketManager.Connect(const pAddress: string; const pPort: integer);
  159. var
  160.   lEndPoint: IPEndPoint;
  161.   lHostEntry: IPHostEntry;
  162. begin
  163.   try
  164.     FStopEvent.Reset;
  165.     lHostEntry := Dns.Resolve(pAddress);
  166.     if Length(lHostEntry.AddressList) <> 0 then
  167.     begin
  168.       lEndPoint := IPEndPoint.Create(lHostEntry.AddressList[0], pPort);
  169.       FClientSocket := Socket.Create(lEndPoint.AddressFamily, SocketType.Stream, ProtocolType.TCP);
  170.       //No need to connect asynchronously
  171.       FClientSocket.Connect(lEndPoint);
  172.  
  173.       //Pool the send and seceive thread entry points
  174.       ThreadPool.QueueUserWorkItem(ReceiveThreadEntryPoint);
  175.       ThreadPool.QueueUserWorkItem(SendThreadEntryPoint);
  176.       FConnected := True;
  177.     end;
  178.   except on e: Exception do
  179.     FOnConnectError(Self, e);
  180.   end;
  181. end;
  182.  
  183. /// <summary>
  184. /// Send text through the socket connection.
  185. /// </summary>
  186. /// <param name="pText">The text to send.</param>
  187. procedure TSocketManager.SendText(const pText: string);
  188. begin
  189.   if Assigned(FClientSocket) and FClientSocket.Connected then
  190.   begin
  191.     FTransmitLock.AcquireWriterLock(-1);
  192.     try
  193.       FTransmitQueue.Enqueue(pText);
  194.     finally
  195.       FTransmitLock.ReleaseWriterLock;
  196.     end;
  197.   end;
  198. end;
  199.  
  200. /// <summary>
  201. /// Disconnect the client socket, and signal the worker threads to stop.
  202. /// </summary>
  203. procedure TSocketManager.Disconnect;
  204. begin
  205.   //signal the threads to end
  206.     FStopEvent.&Set;
  207.   if Assigned(FClientSocket) then
  208.   begin
  209.     //Disable sending and receiving on the socket
  210.     FClientSocket.Shutdown(SocketShutdown.Both);
  211.     //Close the socket
  212.     FClientSocket.Close;
  213.     FreeAndNil(FClientSocket);
  214.   end;
  215.   FConnected := False;
  216. end;
  217.  
  218. //***** TCP/IP Asynchronous Callback Methods *****
  219.  
  220. /// <summary>
  221. /// Callback method called by the asynchronous BeginSend method.
  222. /// </summary>
  223. procedure TSocketManager.SendCallback(pAsyncResult: IAsyncResult);
  224. var
  225.   lSocket: Socket;
  226. begin
  227.   lSocket := Socket(pAsyncResult.AsyncState);
  228.   try
  229.     lSocket.EndSend(pAsyncResult);
  230.   except
  231.     on e: Exception do
  232.       if Assigned(FOnSendError) then
  233.         FOnSendError(lSocket, e);
  234.   end;
  235. end;
  236.  
  237. /// <summary>
  238. /// Callback method called by the asynchronous BeginReceive method.
  239. /// </summary>
  240. procedure TSocketManager.ReceiveCallback(pAsyncResult: IAsyncResult);
  241. var
  242.   lBytesRead: Integer;
  243.   lClient: Socket;
  244.   lState: TStateObject;
  245. begin
  246.   lClient := nil;
  247.   try
  248.     if FStopEvent.WaitOne(10, true) then
  249.       exit;
  250.  
  251.     lState := TStateObject(pAsyncResult.AsyncState);
  252.     lClient := lState.WorkSocket;
  253.     if Assigned(lClient) and lClient.Connected then
  254.     begin
  255.       //We are still connected
  256.       lBytesRead := lClient.EndReceive(pAsyncResult);
  257.       if (lBytesRead > 0) then
  258.       begin
  259.         if Assigned(FOnDataReceived) then
  260.           //Notify that data has been received
  261.           FOnDataReceived(lClient, Encoding.ASCII.GetString(lState.Buffer, 0, lBytesRead));
  262.         //Keep listening for more data
  263.         lClient.BeginReceive(lState.Buffer, 0, lState.BufferSize, SocketFlags.None, ReceiveCallback, lState);
  264.       end
  265.       else
  266.       begin
  267.         FClientSocket.Shutdown(SocketShutdown.Both);
  268.         FClientSocket.Close;
  269.         FOnClientDisconnected(Self, FClientSocket);
  270.       end;
  271.     end;
  272.   except
  273.     on e: Exception do
  274.       if Assigned(FOnReceiveError) then
  275.         FOnReceiveError(lClient, e);
  276.   end;
  277. end;
  278.  
  279. /// <summary>
  280. /// Callback method called by the asynchronous BeginAccept method.
  281. /// </summary>
  282. procedure TSocketManager.AcceptCallback(pAsyncResult: IAsyncResult);
  283. var
  284.   lListener: Socket;
  285. begin
  286.   FConnectEvent.&Set;
  287.  
  288.   //Get the socket that handles the client request.
  289.   lListener := Socket(pAsyncResult.AsyncState);
  290.   FClientSocket := lListener.EndAccept(pAsyncResult);
  291.   if Assigned(FOnClientConnected) then
  292.     FOnClientConnected(FServerSocket, FClientSocket);
  293.  
  294.   //Pool the send and seceive thread entry points
  295.   ThreadPool.QueueUserWorkItem(ReceiveThreadEntryPoint);
  296.   ThreadPool.QueueUserWorkItem(SendThreadEntryPoint);
  297.   FServerSocket.BeginAccept(AcceptCallback, FServerSocket);
  298. end;
  299.  
  300. //***** TCP/IP Thread Entry Point Methods *****
  301.  
  302. /// <summary>
  303. /// Send Thread entry point.
  304. /// </summary>
  305. procedure TSocketManager.SendThreadEntryPoint(pState: TObject);
  306. var
  307.   lWorkQueue: Queue;
  308.   i: integer;
  309.   lStateObject: TStateObject;
  310.   lBuffer: TBytes;
  311.   lAsyncResult: IAsyncResult;
  312. begin
  313.   try
  314.     lWorkQueue := Queue.Create;
  315.  
  316.     while True do
  317.     begin
  318.       if FStopEvent.WaitOne(10, true) then
  319.         break
  320.       else if Assigned(FClientSocket) and FClientSocket.Connected then
  321.       begin
  322.         //We are still connected, so process the send queue
  323.         FTransmitLock.AcquireWriterLock(-1);
  324.         try
  325.           try
  326.             for i := 0 to FTransmitQueue.Count -1 do
  327.               lWorkQueue.Enqueue(FTransmitQueue.DeQueue);
  328.           except
  329.             on e: Exception do
  330.               if Assigned(FOnSendError) then
  331.                 FOnSendError(FClientSocket, e);
  332.           end;
  333.         finally
  334.           FTransmitLock.ReleaseWriterLock;
  335.         end;
  336.  
  337.         //Loop through the work queue and send all messages
  338.         for i := 0 to lWorkQueue.Count -1 do
  339.         begin
  340.           //Create the State object and buffer the string
  341.           lStateObject := TStateObject.Create(FClientSocket);
  342.           lBuffer := Encoding.ASCII.GetBytes(lWorkQueue.DeQueue.ToString);
  343.  
  344.           //Send the contents of the buffer
  345.           lAsyncResult := FClientSocket.BeginSend(lBuffer, 0, Length(lBuffer),
  346.             SocketFlags.None, SendCallback, FClientSocket);
  347.         end;
  348.       end;
  349.     end;
  350.   except on e: Exception do
  351.     if Assigned(FOnSendError) then
  352.       FOnSendError(FClientSocket, e);
  353.   end;
  354. end;
  355.  
  356. /// <summary>
  357. /// Receive Thread entry point.
  358. /// </summary>
  359. procedure TSocketManager.ReceiveThreadEntryPoint(pState: TObject);
  360. var
  361.   lAsyncResult: IAsyncResult;
  362.   lStateObject: TStateObject;
  363. begin
  364.     try
  365.     while True do
  366.     begin
  367.       if Assigned(FClientSocket) then
  368.         if FClientSocket.Connected then
  369.         try
  370.           //Start the receive operation
  371.           lStateObject := TStateObject.Create(FClientSocket);
  372.           lAsyncResult := FClientSocket.BeginReceive(lStateObject.Buffer,
  373.                   0, lStateObject.BufferSize, SocketFlags.None, ReceiveCallback, lStateObject);
  374.           if FStopEvent.WaitOne(10, true) then
  375.             //Stop event was signalled, so break out of the loop
  376.             break;
  377.         except on
  378.           e: Exception do
  379.             if Assigned(FOnReceiveError) then
  380.               FOnReceiveError(FClientSocket, e);
  381.         end
  382.         else
  383.         begin
  384.           if Assigned(FOnClientDisconnected) then
  385.             FOnClientDisconnected(FServerSocket, FClientSocket);
  386.         end;
  387.     end;
  388.   except
  389.     on e: Exception do
  390.       if Assigned(FOnReceiveError) then
  391.         FOnReceiveError(FClientSocket, e);
  392.   end;
  393. end;
  394.  
  395. /// <summary>
  396. /// Listen thread entry point.
  397. /// </summary>
  398. procedure TSocketManager.ListenThreadEntryPoint(pState: TObject);
  399. begin
  400.   try
  401.     while True do
  402.     try
  403.       //Set the event to nonsignaled state.
  404.       FConnectEvent.Reset;
  405.       //Listen, allowing a queue of 1 connection
  406.       FServerSocket.Listen(1);
  407.       FServerSocket.BeginAccept(AcceptCallback, FServerSocket);
  408.  
  409.       if FStopEvent.WaitOne(10, true) then
  410.         //stop event was signalled, so break out of the loop
  411.         break;
  412.     except on e: Exception do
  413.       if Assigned(FOnListenError) then
  414.         FOnListenError(FServerSocket, e);
  415.     end;
  416.     FServerSocket.Close;
  417.   except on e: Exception do
  418.     if Assigned(FOnListenError) then
  419.       FOnListenError(FServerSocket, e);
  420.   end;
  421. end;
  422.  
  423. end.
  424.